package network

import (
	"gitee.com/amoyx/cloud-provider/pkg/cloud/tencent/models"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	tvpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
)

type VpnGreeter interface {
	VPN(region string) VpnInterface
}

type VpnInterface interface {
	CreateIPSECVPN(args models.VpnRequest) (result models.CreateResponseParams, err error)
	CreateSSLVPN(args models.VpnRequest) (result models.CreateResponseParams, err error)
	CreateCCNVPN(args models.VpnRequest) (result models.CreateResponseParams, err error)
	CreateSSLCCNVPN(args models.VpnRequest) (result models.CreateResponseParams, err error)
	Get(id string) (result models.VpnResponse, err error)
	DeleteVpnGateway(id string) (err error)
	ListByName(name string) (list []models.VpnResponse, err error)
	ListByVpc(vpcId string) (list []models.VpnResponse, err error)
	List() (list []models.VpnResponse, err error)
	QuerySSLVPNPrice(args models.VpnRequest) (result models.VpnPrice, err error)
	QueryIPSECVPNPrice(args models.VpnRequest) (result models.VpnPrice, err error)
	CreateCustomerGateway(name string, ipaddr string) (result models.CreateResponseParams, err error)
	GetVPNCustomer(id string) (result models.VPNCustomerGateway, err error)
	ListVPNCustomer() (list []models.VPNCustomerGateway, err error)
	ListVPNCustomerByName(name string) (list []models.VPNCustomerGateway, err error)
	DeleteCustomerGateway(id string) (err error)
	CreateVpnConnection(args models.VpnConnectionRequest) (result models.CreateResponseParams, err error)
	DeleteVpnConnection(vpnGatewayId string, vpnConnectionId string) (err error)
	GetVpnConnection(id string) (result *tvpc.VpnConnection, err error)
	ListVpnConnection() (list []*tvpc.VpnConnection, err error)
	ListVpnConnectionByName(name string) (list []*tvpc.VpnConnection, err error)
}

type vpn struct {
	network *Network
	region  string
	client  *tvpc.Client
}

func newVpn(network *Network, region string) VpnInterface {
	v := &vpn{
		network: network,
		region:  region,
	}
	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = string(VpcEndPoint)
	client, _ := tvpc.NewClient(v.network.Credential(), v.region, cpf)
	v.client = client
	return v
}

// CreateIPSECVPN 创建ipsec类型 vpn
func (v *vpn) CreateIPSECVPN(args models.VpnRequest) (result models.CreateResponseParams, err error) {
	return v.addVpn("IPSEC", args)
}

// CreateSSLVPN 创建ssl类型vpn
func (v *vpn) CreateSSLVPN(args models.VpnRequest) (result models.CreateResponseParams, err error) {
	return v.addVpn("SSL", args)
}

// CreateCCNVPN 创建云联网型IPSEC VPN网关
func (v *vpn) CreateCCNVPN(args models.VpnRequest) (result models.CreateResponseParams, err error) {
	return v.addVpn("CCN", args)
}

// CreateSSLCCNVPN 创建云联网型SSL VPN网关
func (v *vpn) CreateSSLCCNVPN(args models.VpnRequest) (result models.CreateResponseParams, err error) {
	return v.addVpn("SSL_CCN", args)
}

// CreateCustomerGateway 创建对端网关
func (v *vpn) CreateCustomerGateway(name string, ipaddr string) (result models.CreateResponseParams, err error) {
	request := tvpc.NewCreateCustomerGatewayRequest()
	request.CustomerGatewayName = common.StringPtr(name)
	request.IpAddress = common.StringPtr(ipaddr)
	response, err := v.client.CreateCustomerGateway(request)
	if err != nil {
		return
	}
	result.RequestId = *response.Response.RequestId
	result.InstanceSet = append(result.InstanceSet, *response.Response.CustomerGateway.CustomerGatewayId)
	return
}

// DeleteCustomerGateway 删除VPN对端网关
func (v *vpn) DeleteCustomerGateway(id string) (err error) {
	request := tvpc.NewDeleteCustomerGatewayRequest()
	request.CustomerGatewayId = common.StringPtr(id)
	_, err = v.client.DeleteCustomerGateway(request)
	return
}

// DeleteVpnGateway 删除VPN网关
func (v *vpn) DeleteVpnGateway(id string) (err error) {
	request := tvpc.NewDeleteVpnGatewayRequest()
	request.VpnGatewayId = common.StringPtr(id)
	_, err = v.client.DeleteVpnGateway(request)
	return
}

// DeleteVpnConnection 删除VPN连接通道
func (v *vpn) DeleteVpnConnection(vpnGatewayId string, vpnConnectionId string) (err error) {
	request := tvpc.NewDeleteVpnConnectionRequest()
	request.VpnGatewayId = common.StringPtr(vpnGatewayId)
	request.VpnConnectionId = common.StringPtr(vpnConnectionId)
	_, err = v.client.DeleteVpnConnection(request)
	return
}

func (v *vpn) QuerySSLVPNPrice(args models.VpnRequest) (result models.VpnPrice, err error) {
	return v.inquiryPrice("SSL", args)
}

func (v *vpn) QueryIPSECVPNPrice(args models.VpnRequest) (result models.VpnPrice, err error) {
	return v.inquiryPrice("IPSEC", args)
}

func (v *vpn) inquiryPrice(vpnType string, args models.VpnRequest) (result models.VpnPrice, err error) {
	request := tvpc.NewInquiryPriceCreateVpnGatewayRequest()
	switch {
	case vpnType == "IPSEC":
		request.InstanceChargeType = common.StringPtr(args.InstanceChargeType)
		if args.InstanceChargeType == "PREPAID" {
			request.InstanceChargePrepaid = &tvpc.InstanceChargePrepaid{
				Period:    common.Uint64Ptr(args.Period),
				RenewFlag: common.StringPtr("NOTIFY_AND_AUTO_RENEW"),
			}
		}
	case vpnType == "SSL":
		request.InstanceChargeType = common.StringPtr("POSTPAID_BY_HOUR")
		request.MaxConnection = common.Uint64Ptr(args.MaxConnection)
		args.InstanceChargeType = "POSTPAID_BY_HOUR"
	default:
		request.InstanceChargeType = common.StringPtr("POSTPAID_BY_HOUR")
		args.InstanceChargeType = "POSTPAID_BY_HOUR"
	}
	request.InternetMaxBandwidthOut = common.Uint64Ptr(args.InternetMaxBandwidthOut)
	request.Type = common.StringPtr(vpnType)
	response, err := v.client.InquiryPriceCreateVpnGateway(request)
	if err != nil {
		return
	}
	price := response.Response.Price
	if args.InstanceChargeType == "POSTPAID_BY_HOUR" {
		result.BandwidthPrice.OriginalPrice = *price.BandwidthPrice.OriginalPrice
		result.BandwidthPrice.DiscountPrice = *price.BandwidthPrice.DiscountPrice
		result.BandwidthPrice.UnitPrice = *price.BandwidthPrice.UnitPrice
		result.BandwidthPrice.ChargeUnit = *price.BandwidthPrice.ChargeUnit
	}
	result.InstancePrice.OriginalPrice = *price.InstancePrice.OriginalPrice
	result.InstancePrice.DiscountPrice = *price.InstancePrice.DiscountPrice
	result.InstancePrice.UnitPrice = *price.InstancePrice.UnitPrice
	result.InstancePrice.ChargeUnit = *price.InstancePrice.ChargeUnit
	return result, nil
}

func (v *vpn) addVpn(vpnType string, args models.VpnRequest) (result models.CreateResponseParams, err error) {
	// VPN网关类型，默认为IPSEC。类型为 “IPSEC”, “SSL”, “CCN”为云联网型IPSEC VPN网关，值“SSL_CCN”为云联网型SSL VPN网关。
	request := tvpc.NewCreateVpnGatewayRequest()
	request.VpcId = common.StringPtr(args.VpcId)
	request.VpnGatewayName = common.StringPtr(args.VpnGatewayName)
	switch {
	case vpnType == "IPSEC" || vpnType == "CCN":
		request.InstanceChargeType = common.StringPtr(args.InstanceChargeType)
		if args.InstanceChargeType == "PREPAID" {
			request.InstanceChargePrepaid = &tvpc.InstanceChargePrepaid{
				Period:    common.Uint64Ptr(args.Period),
				RenewFlag: common.StringPtr("NOTIFY_AND_AUTO_RENEW"),
			}
		}
	case vpnType == "SSL" || vpnType == "SSL_CCN":
		request.InstanceChargeType = common.StringPtr("POSTPAID_BY_HOUR")
		request.MaxConnection = common.Uint64Ptr(args.MaxConnection)
	default:
		request.InstanceChargeType = common.StringPtr("POSTPAID_BY_HOUR")
	}
	request.InternetMaxBandwidthOut = common.Uint64Ptr(args.InternetMaxBandwidthOut)
	request.Zone = common.StringPtr(args.Zone)
	request.Type = common.StringPtr(vpnType)
	var tags []*tvpc.Tag
	for key, val := range args.Tags {
		tags = append(tags, &tvpc.Tag{
			Key:   common.StringPtr(key),
			Value: common.StringPtr(val),
		})
	}
	response, err := v.client.CreateVpnGateway(request)
	result.RequestId = *response.Response.RequestId
	return
}

// CreateVpnConnection 创建VPN连接通道
func (v *vpn) CreateVpnConnection(args models.VpnConnectionRequest) (result models.CreateResponseParams, err error) {
	request := tvpc.NewCreateVpnConnectionRequest()
	request.VpcId = common.StringPtr(args.VpcId)
	request.VpnGatewayId = common.StringPtr(args.VpnGatewayId)
	request.CustomerGatewayId = common.StringPtr(args.CustomerGatewayId)
	request.VpnConnectionName = common.StringPtr(args.VpnConnectionName)
	request.PreShareKey = common.StringPtr(args.PreShareKey)
	request.IKEOptionsSpecification = &tvpc.IKEOptionsSpecification{
		PropoEncryAlgorithm:  common.StringPtr(args.IKEOptionsSpecification.PropoEncryAlgorithm),
		PropoAuthenAlgorithm: common.StringPtr(args.IKEOptionsSpecification.PropoAuthenAlgorithm),
		ExchangeMode:         common.StringPtr(args.IKEOptionsSpecification.ExchangeMode),
		LocalIdentity:        common.StringPtr(args.IKEOptionsSpecification.LocalIdentity),
		RemoteIdentity:       common.StringPtr(args.IKEOptionsSpecification.RemoteIdentity),
		LocalAddress:         common.StringPtr(args.IKEOptionsSpecification.LocalAddress),
		RemoteAddress:        common.StringPtr(args.IKEOptionsSpecification.RemoteAddress),
		LocalFqdnName:        common.StringPtr(args.IKEOptionsSpecification.LocalFqdnName),
		RemoteFqdnName:       common.StringPtr(args.IKEOptionsSpecification.RemoteFqdnName),
		DhGroupName:          common.StringPtr(args.IKEOptionsSpecification.DhGroupName),
		IKESaLifetimeSeconds: common.Uint64Ptr(args.IKEOptionsSpecification.IKESaLifetimeSeconds),
		IKEVersion:           common.StringPtr(args.IKEOptionsSpecification.IKEVersion),
	}
	request.IPSECOptionsSpecification = &tvpc.IPSECOptionsSpecification{
		EncryptAlgorithm:       common.StringPtr(args.IPSECOptionsSpecification.EncryptAlgorithm),
		IntegrityAlgorith:      common.StringPtr(args.IPSECOptionsSpecification.IntegrityAlgorith),
		IPSECSaLifetimeSeconds: common.Uint64Ptr(args.IPSECOptionsSpecification.IPSECSaLifetimeSeconds),
		PfsDhGroup:             common.StringPtr(args.IPSECOptionsSpecification.PfsDhGroup),
		IPSECSaLifetimeTraffic: common.Uint64Ptr(args.IPSECOptionsSpecification.IPSECSaLifetimeTraffic),
	}
	request.EnableHealthCheck = common.BoolPtr(args.EnableHealthCheck)
	request.HealthCheckLocalIp = common.StringPtr(args.HealthCheckLocalIp)
	request.HealthCheckRemoteIp = common.StringPtr(args.HealthCheckRemoteIp)
	request.RouteType = common.StringPtr(args.RouteType)
	request.NegotiationType = common.StringPtr(args.NegotiationType)
	request.DpdEnable = common.Int64Ptr(args.DpdEnable)
	request.DpdTimeout = common.StringPtr(args.DpdTimeout)
	request.DpdAction = common.StringPtr(args.DpdAction)
	response, err := v.client.CreateVpnConnection(request)
	if err != nil {
		return
	}
	result.RequestId = *response.Response.RequestId
	result.InstanceSet = append(result.InstanceSet, *response.Response.VpnConnection.VpnConnectionId)
	return
}

func (v *vpn) Get(id string) (result models.VpnResponse, err error) {
	list, err := v.query(map[string]string{"vpn-gateway-id": id})
	if len(list) > 0 {
		result = list[0]
	}
	return
}

func (v *vpn) ListByName(name string) (list []models.VpnResponse, err error) {
	return v.query(map[string]string{"vpn-gateway-name": name})
}

func (v *vpn) ListByVpc(vpcId string) (list []models.VpnResponse, err error) {
	return v.query(map[string]string{"vpc-id": vpcId})
}
func (v *vpn) List() (list []models.VpnResponse, err error) {
	return v.query(map[string]string{})
}

func (v *vpn) query(args map[string]string) (list []models.VpnResponse, err error) {
	request := tvpc.NewDescribeVpnGatewaysRequest()
	var filters []*tvpc.FilterObject
	for key, val := range args {
		filters = append(filters, &tvpc.FilterObject{
			Name:   common.StringPtr(key),
			Values: common.StringPtrs([]string{val}),
		})
	}
	response, err := v.client.DescribeVpnGateways(request)
	if err != nil {
		return
	}
	for _, row := range response.Response.VpnGatewaySet {
		list = append(list, models.VpnResponse{
			VpnGatewayId:            *row.VpnGatewayId,
			VpcId:                   *row.VpcId,
			VpnGatewayName:          *row.VpnGatewayName,
			Type:                    *row.Type,
			State:                   *row.State,
			PublicIpAddress:         *row.PublicIpAddress,
			RenewFlag:               *row.RenewFlag,
			InstanceChargeType:      *row.InstanceChargeType,
			InternetMaxBandwidthOut: *row.InternetMaxBandwidthOut,
			CreatedTime:             *row.CreatedTime,
			ExpiredTime:             *row.ExpiredTime,
			IsAddressBlocked:        *row.IsAddressBlocked,
			Zone:                    *row.Zone,
			Version:                 *row.Version,
			NetworkInstanceId:       *row.NetworkInstanceId,
			MaxConnection:           *row.MaxConnection,
		})
	}
	return
}

func (v *vpn) ListVPNCustomer() (list []models.VPNCustomerGateway, err error) {
	return v.queryCustomerVpn(map[string]string{})
}

func (v *vpn) ListVPNCustomerByName(name string) (list []models.VPNCustomerGateway, err error) {
	return v.queryCustomerVpn(map[string]string{"customer-gateway-name": name})
}

func (v *vpn) GetVPNCustomer(id string) (result models.VPNCustomerGateway, err error) {
	list, err := v.queryCustomerVpn(map[string]string{"customer-gateway-id": id})
	if len(list) > 0 {
		result = list[0]
	}
	return
}

func (v *vpn) queryCustomerVpn(args map[string]string) (list []models.VPNCustomerGateway, err error) {
	request := tvpc.NewDescribeCustomerGatewaysRequest()
	var filters []*tvpc.Filter
	for key, val := range args {
		filters = append(filters, &tvpc.Filter{
			Name:   common.StringPtr(key),
			Values: common.StringPtrs([]string{val}),
		})
	}
	request.Filters = filters
	request.Offset = common.Uint64Ptr(0)
	request.Limit = common.Uint64Ptr(100)
	response, err := v.client.DescribeCustomerGateways(request)
	if err != nil {
		return
	}
	for _, row := range response.Response.CustomerGatewaySet {
		list = append(list, models.VPNCustomerGateway{
			CustomerGatewayId:   *row.CustomerGatewayId,
			CustomerGatewayName: *row.CustomerGatewayName,
			IpAddress:           *row.IpAddress,
			CreatedTime:         *row.CreatedTime,
		})
	}
	return
}

func (v *vpn) ListVpnConnection() (list []*tvpc.VpnConnection, err error) {
	return v.queryVpnConnection(map[string]string{})
}

func (v *vpn) ListVpnConnectionByName(name string) (list []*tvpc.VpnConnection, err error) {
	return v.queryVpnConnection(map[string]string{"vpn-connection-name": name})
}

func (v *vpn) GetVpnConnection(id string) (result *tvpc.VpnConnection, err error) {
	list, err := v.queryVpnConnection(map[string]string{"vpn-connection-id": id})
	if len(list) > 0 {
		result = list[0]
	}
	return
}

func (v *vpn) queryVpnConnection(args map[string]string) (list []*tvpc.VpnConnection, err error) {
	request := tvpc.NewDescribeVpnConnectionsRequest()
	var filters []*tvpc.Filter
	for key, val := range args {
		filters = append(filters, &tvpc.Filter{
			Name:   common.StringPtr(key),
			Values: common.StringPtrs([]string{val}),
		})
	}
	request.Filters = filters
	request.Offset = common.Uint64Ptr(0)
	request.Limit = common.Uint64Ptr(100)
	response, err := v.client.DescribeVpnConnections(request)
	if err != nil {
		return
	}
	list = response.Response.VpnConnectionSet
	return
}
